/*
 * (C) Copyright 2020 Loongson <tanghaifeng-gz@loongson.cn>
 *
 * SPDX-License-Identifier:	GPL-2.0+
 */

/*
 * Designware ethernet IP driver for u-boot
 */

#include <common.h>
#include <miiphy.h>
#include <malloc.h>
#include <linux/compiler.h>
#include <linux/err.h>
#include <asm/io.h>

#include "loongson-eth-gmac.h"
#include "loongson-eth-dma.h"

static void *platform_memalign(size_t bytes)
{
#ifdef CONFIG_CPU_LOONGSON1
	//ls1x cpu内存分配必须使用PAGE_SIZE（4KB）大小的字节对齐，内存管理的页大小
	return (void *)CKSEG1ADDR(memalign(PAGE_SIZE, bytes));
#else
	return (void *)memalign(PAGE_SIZE, bytes);
#endif
}

static void platform_memfree(void *ptr)
{
#ifdef CONFIG_CPU_LOONGSON1
	free((void *)(((u32)ptr & 0x0fffffff) | 0x80000000));
//	free((void *)CKSEG0ADDR((u32)ptr));
#else
	free(ptr);
#endif
}

static int dw_mdio_read(struct mii_dev *bus, int addr, int devad, int reg)
{
	struct eth_mac_regs *mac_p = bus->priv;
	ulong start;
	u16 miiaddr;
	int timeout = CONFIG_MDIO_TIMEOUT;

	miiaddr = ((addr << MIIADDRSHIFT) & MII_ADDRMSK) |
		  ((reg << MIIREGSHIFT) & MII_REGMSK);

	writel(miiaddr | MII_CLKRANGE_150_250M | MII_BUSY, &mac_p->miiaddr);

	start = get_timer(0);
	while (get_timer(start) < timeout) {
		if (!(readl(&mac_p->miiaddr) & MII_BUSY))
			return readl(&mac_p->miidata);
		udelay(10);
	};

	return -1;
}

static int dw_mdio_write(struct mii_dev *bus, int addr, int devad, int reg,
			u16 val)
{
	struct eth_mac_regs *mac_p = bus->priv;
	ulong start;
	u16 miiaddr;
	int ret = -1, timeout = CONFIG_MDIO_TIMEOUT;

	writel(val, &mac_p->miidata);
	miiaddr = ((addr << MIIADDRSHIFT) & MII_ADDRMSK) |
		  ((reg << MIIREGSHIFT) & MII_REGMSK) | MII_WRITE;

	writel(miiaddr | MII_CLKRANGE_150_250M | MII_BUSY, &mac_p->miiaddr);

	start = get_timer(0);
	while (get_timer(start) < timeout) {
		if (!(readl(&mac_p->miiaddr) & MII_BUSY)) {
			ret = 0;
			break;
		}
		udelay(10);
	};

	return ret;
}

static int dw_mdio_init(char *name, struct eth_mac_regs *mac_regs_p)
{
	struct mii_dev *bus = mdio_alloc();

	if (!bus) {
		printf("Failed to allocate MDIO bus\n");
		return -1;
	}

	bus->read = dw_mdio_read;
	bus->write = dw_mdio_write;
	sprintf(bus->name, name);

	bus->priv = (void *)mac_regs_p;

	return mdio_register(bus);
}

static int tx_descs_init(struct eth_device *dev)
{
	struct dw_eth_dev *priv = dev->priv;
	struct eth_dma_regs *dma_p = priv->dma_regs_p;
	struct dwdmadev *dmadev = priv->dmadev;
	s32 i;
	struct dmamacdescr *first_desc = NULL;

	first_desc = (struct dmamacdescr *)platform_memalign(sizeof(struct dmamacdescr) * CONFIG_TX_DESCR_NUM);
	if (first_desc == NULL) {
		printf("Error in Tx Descriptors memory allocation\n");
		return -ENOMEM;
	}

	dmadev->TxDescCount = CONFIG_TX_DESCR_NUM;
	dmadev->TxDesc      = first_desc;

	for (i=0; i<dmadev->TxDescCount; i++) {
		synopGMAC_tx_desc_init_ring(dmadev->TxDesc + i, i == dmadev->TxDescCount-1);
	}

	dmadev->TxNext = 0;
	dmadev->TxBusy = 0;
	dmadev->TxNextDesc = dmadev->TxDesc;
	dmadev->TxBusyDesc = dmadev->TxDesc;

	writel((dma_addr_t)virt_to_phys((void *)first_desc), &dma_p->txdesclistaddr);
	udelay(100);readl(&dma_p->txdesclistaddr);

	return 0;
}

static int rx_descs_init(struct eth_device *dev)
{
	struct dw_eth_dev *priv = dev->priv;
	struct eth_dma_regs *dma_p = priv->dma_regs_p;
	struct dwdmadev *dmadev = priv->dmadev;
	struct dmamacdescr *first_desc = NULL;
	void *skb;
	int i, ret;

	first_desc = (struct dmamacdescr *)platform_memalign(sizeof(struct dmamacdescr) * CONFIG_RX_DESCR_NUM);
	if (first_desc == NULL) {
		printf("Error in Rx Descriptor Memory allocation in Ring mode\n");
		return -ENOMEM;
	}

	dmadev->RxDescCount = CONFIG_RX_DESCR_NUM;
	dmadev->RxDesc      = first_desc;

	for (i=0; i<dmadev->RxDescCount; i++) {
		synopGMAC_rx_desc_init_ring(dmadev->RxDesc + i, i == dmadev->RxDescCount-1);
	}

	dmadev->RxNext = 0;
	dmadev->RxBusy = 0;
	dmadev->RxNextDesc = dmadev->RxDesc;
	dmadev->RxBusyDesc = dmadev->RxDesc;

	writel((dma_addr_t)virt_to_phys((void *)first_desc), &dma_p->rxdesclistaddr);
	udelay(100);readl(&dma_p->rxdesclistaddr);

	do {
		skb = platform_memalign(RX_TOTAL_BUFSIZE);
		if (!skb) {
			printf("Error in skb buffer allocation %s\n", __func__);
			return -ENOMEM;
		}
		ret = synopGMAC_set_rx_qptr(dmadev, virt_to_phys(skb), RX_TOTAL_BUFSIZE, (u32)skb, 0, 0, 0);
		if (ret < 0) {
			platform_memfree(skb);
		}
	} while((ret >= 0) && (ret < (CONFIG_RX_DESCR_NUM - 1)));

	return 0;
}

static int dw_write_hwaddr(struct eth_device *dev)
{
	struct dw_eth_dev *priv = dev->priv;
	struct eth_mac_regs *mac_p = priv->mac_regs_p;
	u32 macid_lo, macid_hi;
	u8 *mac_id = &dev->enetaddr[0];

	macid_lo = mac_id[0] + (mac_id[1] << 8) + (mac_id[2] << 16) +
		   (mac_id[3] << 24);
	macid_hi = mac_id[4] + (mac_id[5] << 8);

	writel(macid_hi, &mac_p->macaddr0hi);
	writel(macid_lo, &mac_p->macaddr0lo);

	return 0;
}

static void dw_adjust_link(struct eth_mac_regs *mac_p,
			   struct phy_device *phydev)
{
	u32 conf = readl(&mac_p->conf) | FRAMEBURSTENABLE | DISABLERXOWN;

	if (!phydev->link) {
		printf("%s: No link.\n", phydev->dev->name);
		return;
	}

	if (phydev->speed != 1000)
		conf |= MII_PORTSELECT;

	if (phydev->speed == 100)
		conf |= FES_100;

	if (phydev->duplex)
		conf |= FULLDPLXMODE;

	writel(conf, &mac_p->conf);

	printf("Speed: %d, %s duplex%s\n", phydev->speed,
	       (phydev->duplex) ? "full" : "half",
	       (phydev->port == PORT_FIBRE) ? ", fiber mode" : "");
}

static void dw_eth_halt(struct eth_device *dev)
{
	struct dw_eth_dev *priv = dev->priv;
	struct eth_mac_regs *mac_p = priv->mac_regs_p;
	struct eth_dma_regs *dma_p = priv->dma_regs_p;

	writel(readl(&mac_p->conf) & ~(RXENABLE | TXENABLE), &mac_p->conf);
	writel(readl(&dma_p->opmode) & ~(RXSTART | TXSTART), &dma_p->opmode);

	phy_shutdown(priv->phydev);
}

static int dw_eth_init(struct eth_device *dev, bd_t * bd)
{
	struct dw_eth_dev *priv = dev->priv;
	struct eth_mac_regs *mac_p = priv->mac_regs_p;
	struct eth_dma_regs *dma_p = priv->dma_regs_p;
	unsigned int start;

	writel(readl(&dma_p->busmode) | DMAMAC_SRST, &dma_p->busmode);
	udelay(100);readl(&dma_p->busmode);

	start = get_timer(0);
	while (readl(&dma_p->busmode) & DMAMAC_SRST) {
		if (get_timer(start) >= CONFIG_MACRESET_TIMEOUT) {
			printf("DMA reset timeout\n");
			return -1;
		}

		mdelay(100);
	};

	/* Soft reset above clears HW address registers.
	 * So we have to set it here once again */
	dw_write_hwaddr(dev);

	tx_descs_init(dev);
	rx_descs_init(dev);

#ifdef CONFIG_CPU_LOONGSON1C
	writel(FIXEDBURST | PRIORXTX_41 | DMA_PBL | (2 << 2), &dma_p->busmode);
#else
	writel(FIXEDBURST | PRIORXTX_41 | DMA_PBL | (1 << 2), &dma_p->busmode);
#endif
	udelay(100);readl(&dma_p->busmode);

	writel(readl(&dma_p->opmode) | FLUSHTXFIFO | STOREFORWARD,
	       &dma_p->opmode);
	udelay(100);readl(&dma_p->opmode);

	writel(readl(&dma_p->opmode) | RXSTART | TXSTART, &dma_p->opmode);
	udelay(100);readl(&dma_p->opmode);

	/* Start up the PHY */
	if (phy_startup(priv->phydev)) {
		printf("Could not initialize PHY %s\n",
		       priv->phydev->dev->name);
		return -1;
	}

	dw_adjust_link(mac_p, priv->phydev);

	if (!priv->phydev->link)
		return -1;

	writel(readl(&mac_p->conf) | RXENABLE | TXENABLE, &mac_p->conf);

	return 0;
}

static int _dw_eth_recv(struct eth_device *dev)
{
	struct dw_eth_dev *priv = dev->priv;
	struct dwdmadev *dmadev = priv->dmadev;
	u32 len = 0;
	u32 status;
	s32 desc_index;
	u32 data1, data2;
	u32 dma_addr1, dma_addr2;

	/*Handle the Receive Descriptors*/
	desc_index = synopGMAC_get_rx_qptr(dmadev, &status, &dma_addr1, NULL, &data1, &dma_addr2, NULL, &data2);
	if (desc_index >= 0 && data1 != 0) {
		if (synopGMAC_is_rx_desc_valid(status)) {
			len = synopGMAC_get_rx_desc_frame_length(status) - 4; //Not interested in Ethernet CRC bytes
			NetReceive((unsigned char *)data1, len);
		} else {
		}

		desc_index = synopGMAC_set_rx_qptr(dmadev, virt_to_phys((void *)data1), RX_TOTAL_BUFSIZE, (u32)data1, 0, 0, 0);
		if (desc_index < 0) {
			platform_memfree((void *)data1);
		}
	}
	return len;
}

static int _dw_eth_send(struct eth_device *dev, void *packet, int length)
{
	struct dw_eth_dev *priv = dev->priv;
	struct eth_dma_regs *dma_p = priv->dma_regs_p;
	struct dwdmadev *dmadev = priv->dmadev;
	void *skb;
	int ret;
	u32 status;
	s32 desc_index;
	u32 data1, data2;
	u32 length1, length2;
	u32 dma_addr1, dma_addr2;

	if (!synopGMAC_is_desc_owned_by_dma(dmadev->TxNextDesc)) {
		skb = platform_memalign(TX_TOTAL_BUFSIZE);
		if (!skb) {
			printf("Error in skb buffer allocation %s\n", __func__);
			return -ENOMEM;
		}

		memcpy(skb, packet, length);

		ret = synopGMAC_set_tx_qptr(dmadev, virt_to_phys(skb), length, (u32)skb, 0, 0, 0, 0, (u32 *)&desc_index, NULL);
		if (ret < 0) {
			printf("%s No More Free Tx Descriptors\n", __func__);
			platform_memfree(skb);
			return -ENOMEM;
		}
	} else {
		printf("Next txDesc=%p belongs to DMA don't set it\n", dmadev->TxNextDesc);
	}

	/*Now force the DMA to start transmission*/
	writel(POLL_DATA, &dma_p->txpolldemand);
	udelay(100);readl(&dma_p->txpolldemand);

	/*Handle the transmit Descriptors*/
	do {
		desc_index = synopGMAC_get_tx_qptr(dmadev, &status, &dma_addr1, &length1, &data1, &dma_addr2, &length2, &data2);
		if (desc_index >= 0 && data1 != 0) {
			platform_memfree((void *)data1);
		}
	} while(desc_index >= 0);

	return 0;
}

static int dw_eth_recv(struct eth_device *dev)
{
	return _dw_eth_recv(dev);
}

static int dw_eth_send(struct eth_device *dev, void *packet, int length)
{
	return _dw_eth_send(dev, packet, length);
}

static int dw_phy_init(struct eth_device *dev)
{
	struct dw_eth_dev *priv = dev->priv;
	struct phy_device *phydev;
	int mask = 0xffffffff;
	int addr __maybe_unused;

#ifdef CONFIG_PHY_ADDR
	addr = CONFIG_PHY_ADDR;
	mask = (addr > 0) ? (1 << addr) : 0xffffffff;
#endif

	phydev = phy_find_by_mask(priv->bus, mask, priv->interface);
	if (!phydev)
		return -1;

	phy_connect_dev(phydev, dev);

	phydev->supported &= PHY_GBIT_FEATURES;
	phydev->advertising = phydev->supported;

	priv->phydev = phydev;
	phy_config(phydev);

	return 1;
}

int gmac_initialize(ulong base_addr, u32 interface)
{
	struct eth_device *dev;
	struct dw_eth_dev *priv;

	dev = (struct eth_device *) malloc(sizeof(struct eth_device));
	if (!dev)
		return -ENOMEM;

	/*
	 * Since the priv structure contains the descriptors which need a strict
	 * buswidth alignment, memalign is used to allocate memory
	 */
	priv = (struct dw_eth_dev *) memalign(PAGE_SIZE,
					      sizeof(struct dw_eth_dev));
	if (!priv) {
		free(dev);
		return -ENOMEM;
	}

	priv->dmadev = (struct dwdmadev *)memalign(PAGE_SIZE, sizeof(struct dwdmadev));
	if (!priv->dmadev) {
		free(priv);
		free(dev);
		return -ENOMEM;
	}

	memset(dev, 0, sizeof(struct eth_device));
	memset(priv, 0, sizeof(struct dw_eth_dev));
	memset(priv->dmadev, 0, sizeof(struct dwdmadev));

	sprintf(dev->name, "dwmac.%lx", base_addr);
	dev->iobase = (int)base_addr;
	dev->priv = priv;

	priv->dev = dev;
	priv->mac_regs_p = (struct eth_mac_regs *)base_addr;
	priv->dma_regs_p = (struct eth_dma_regs *)(base_addr +
			DW_DMA_BASE_OFFSET);

	dev->init = dw_eth_init;
	dev->halt = dw_eth_halt;
	dev->send = dw_eth_send;
	dev->recv = dw_eth_recv;
	dev->write_hwaddr = dw_write_hwaddr;

	eth_register(dev);

	priv->interface = interface;

	dw_mdio_init(dev->name, priv->mac_regs_p);
	priv->bus = miiphy_get_dev_by_name(dev->name);

	return dw_phy_init(dev);
}
